//1. assign register for each bbn item
//2. generate mir
//3. format code
use crate::tree::{
    t_exp::Exp,
    t_fn::{Block, BlockType, TFn},
};
use std::collections::HashMap;
pub const INDENTATION: &str = "    ";
pub fn generate_mir(fn_item: TFn) -> Vec<String> {
    let mut registers: HashMap<String, String> = HashMap::new(); //(Identity,_x)
    registers.insert("_0".to_string(), "_0".to_string());
    let mut codes = Vec::<String>::new();

    let fn_signature = generate_fn_signature(&fn_item, &mut registers);
    codes.push(fn_signature);

    let debug_info = generate_debug_info(&fn_item, &registers);
    codes.extend(debug_info);

    let to_add_empty_line = fn_item.params.len() > 0;
    if to_add_empty_line {
        codes.push(String::new());
    }

    let mut bbn_codes = generate_bbns(fn_item, &mut registers);
    if !bbn_codes.is_empty() {
        bbn_codes.remove(0);
    } else {
        if to_add_empty_line {
            codes.remove(0);
        }
    }

    codes.extend(bbn_codes);
    codes.push("}".to_string());

    codes
}

fn generate_bbns(fn_item: TFn, registers: &mut HashMap<String, String>) -> Vec<String> {
    let has_ret = fn_item.return_type.clone() != "()";
    let mut codes = Vec::new();
    let mut bbn_count = fn_item.bbns.iter().count();
    let mut bbns = fn_item.bbns.clone();
    let has_ifret_or_elseret = bbns.iter().any(|bbn| match bbn.bbn_ty {
        crate::tree::t_fn::BlockType::IfRet => true,
        crate::tree::t_fn::BlockType::ElseRet => true,
        _ => false,
    });
    let last_bbn = bbns.last().clone();

    if has_ret && has_ifret_or_elseret {
        if let Some(bbn) = last_bbn {
            if bbn.bbn_ty != BlockType::Fn {
                bbns.push(Block {
                    bbn_name: format!("bb{}", bbn_count),
                    bbn_items: vec![],
                    bbn_ty: BlockType::Fn,
                });
                bbn_count += 1;
            }
        }
    }
    for (i, bbn) in bbns.iter().enumerate() {
        codes.push(String::new());
        let bbn_start = format!("{}{}: {{", INDENTATION, bbn.bbn_name);
        codes.push(bbn_start);
        for item in bbn.bbn_items.iter() {
            match item {
                Exp::ThreeAddrExp(exp) => {
                    let left = exp.result.clone();
                    let left_reg = if let Some(reg) = registers.get(&left) {
                        reg.clone()
                    } else {
                        let new_reg = reserve_register(registers);
                        registers.insert(left.clone(), new_reg.clone());
                        new_reg
                    };
                    codes.push(format!(
                        "{}{} = {}({}, {});",
                        INDENTATION.repeat(2),
                        left_reg,
                        exp.op,
                        registers.get(&exp.left).unwrap_or(&exp.left),
                        registers.get(&exp.right).unwrap_or(&exp.right)
                    ));
                }
                Exp::Assign(left, right) => {
                    let left_reg = if let Some(reg) = registers.get(left) {
                        reg.clone()
                    } else {
                        let new_reg = reserve_register(registers);
                        registers.insert(left.clone(), new_reg.clone());
                        new_reg
                    };
                    codes.push(format!(
                        "{}{} = {};",
                        INDENTATION.repeat(2),
                        left_reg,
                        registers.get(right).unwrap_or(right)
                    ));
                }
                Exp::SwitchInt(c) => {
                    codes.push(format!(
                        "{}switchInt({}) -> [1: bb{}, otherwise: bb{}];",
                        INDENTATION.repeat(2),
                        registers.get(c).unwrap_or(c),
                        i + 1,
                        i + 2
                    ));
                }
                Exp::Goto(block_type, has_else) => {
                    match block_type {
                        crate::tree::t_fn::BlockType::If => {
                            if *has_else {
                                codes.push(format!("{}goto -> bb{}", INDENTATION.repeat(2), i + 2));
                            } else {
                                codes.push(format!("{}goto -> bb{}", INDENTATION.repeat(2), i + 1));
                            }
                        }
                        crate::tree::t_fn::BlockType::Else => {
                            codes.push(format!("{}goto -> bb{}", INDENTATION.repeat(2), i + 1));
                        }
                        crate::tree::t_fn::BlockType::IfRet => {
                            codes.push(format!(
                                "{}goto -> bb{};",
                                INDENTATION.repeat(2),
                                bbn_count - 1
                            ));
                        }
                        crate::tree::t_fn::BlockType::ElseRet => {
                            codes.push(format!(
                                "{}goto -> bb{};",
                                INDENTATION.repeat(2),
                                bbn_count - 1
                            ));
                        }
                        _ => {}
                    };
                }
                _ => {
                    //codes.push(format!("{}{}", INDENTATION.repeat(2), item));
                }
            }
        }
        codes.push(format!("{}{}", INDENTATION, "}"));
    }
    //if has_ret {
    let last_line = codes.remove(codes.len() - 1);
    codes.push(format!("{}{}", INDENTATION.repeat(2), "return;"));
    codes.push(last_line);
    //}
    codes
}

fn generate_debug_info(fn_item: &TFn, registers: &HashMap<String, String>) -> Vec<String> {
    let mut codes = Vec::new();
    for (name, _) in fn_item.params.iter() {
        let debug = format!(
            "debug {} => {};",
            name.clone(),
            registers.get(name).unwrap_or(&"unknown".to_string())
        );
        codes.push(format!("{}{}", INDENTATION, debug));
    }
    codes
}
fn generate_fn_signature(fn_item: &TFn, registers: &mut HashMap<String, String>) -> String {
    let mut fn_signature = format!("fn {}(", fn_item.name);
    let mut fn_params = String::new();
    for (i, param) in fn_item.params.iter().enumerate() {
        if i > 0 {
            fn_params.push_str(", ");
        }
        let new_register = reserve_register(registers);
        fn_params.push_str(&format!("{}: {}", new_register.clone(), param.1));
        employ_register(param.0.clone(), new_register, registers);
    }
    fn_signature.push_str(&fn_params);
    fn_signature.push_str(&format!(") -> {} {{", fn_item.return_type));
    fn_signature
}

fn reserve_register(registers: &mut HashMap<String, String>) -> String {
    let new_register = format!("_{}", registers.len());
    new_register
}

fn employ_register(
    identity: String,
    new_register: String,
    registers: &mut HashMap<String, String>,
) {
    registers.insert(identity, new_register);
}

// fn print_all_registers(registers: &HashMap<String, String>) {
//     for (k, v) in registers.iter() {
//         println!("{} => {}", k, v);
//     }
// }
